<?php

namespace pmvc\annotation;

use Exception;

use ReflectionObject;

/**
 * Abstract class for implementers of the {@link Annotation}
 * interface.  This class provides all of the needs of the
 * {@link ArrayAccess} interface as well as a way to restrict
 * the names of the parameters and ensures that the {@link Annotation}
 * is immutable.
 */
abstract class AbstractAnnotation
	implements Annotation {

	// parameter storage
	private $parameterNames	= Array();
	private $params			= Array();

	/**
	 * Subclasses must return an array of valid parameter
	 * names from this method if overridden. By default
	 * this method looks for the {@code \pmvc\annotation\Params}
	 * annotation on the annotation class and returns it's
	 * {@code names} variable if available.
	 * @return array parameter names
	 */
	protected function getAllowedParameterNames() {
		$params = AnnotationUtil::getAnnotations($this, Array('pmvc\annotation\Params'));
		return !empty($params) ? $params[0]->names : Array();
	}

	/**
	 * This is called for each parameter that is passed to
	 * {@link #setParameters()} to allow subclasses a chance
	 * to validate and potentially transform parameter values.
	 * The value returned by this method is the value that
	 * is stored in the annotation.  This is usefuly for doing
	 * things like ensuring that certain parameters are arrays.
	 * It is safe to assume that this method will never be called
	 * with a parameter name other than the names returned by
	 * {@link getAllowedParameterNames()}
	 * @param string $name the parameter name
	 * @param mixed $value the parameter value
	 */
	protected function validateAndTransformParameter($name, $value) {
		return $value;
	}

	/**
	 * Method for subclasses to use when validating parameters.
	 *
	 * @param mixed $value
	 * @param array $array
	 * @param string $message
	 * @throws InvalidArgumentException on error
	 */
	protected function assertInArray($value, Array $array, $message="Not an allowed value") {
		$values = is_array($value) ? $value : Array($value);
		foreach ($values as $val) {
			$this->assertTrue(in_array($val, $array), $message);
		}
	}

	/**
	 * Method for subclasses to use when validating parameters.
	 *
	 * @param mixed $value
	 * @param string $message
	 * @throws InvalidArgumentException on error
	 */
	protected function assertTrue($value, $message="Invalid value") {
		if (!$value) {
			throw new InvalidArgumentException($message);
		}
	}

	/**
	 * Called immediately after the annotation is
	 * created to initialize it with parameters.
	 * 
	 * @param array $params
	 */
	public function setParameters(Array $params) {

		// get allowed parameter names
		$this->parameterNames = $this->getAllowedParameterNames();
		array_walk($this->parameterNames, function(&$item) { 
			$item = strtolower($item);
		});

		// populate parameters
		foreach ($params as $param => $value) {
			$param = strtolower($param);
			if (!in_array($param, $this->parameterNames)) {
				$refObject = new ReflectionObject($this);
				throw new Exception("Invalid parameter: ".$param." for ".$refObject->getName());
			}
			$this->params[$param] = $this->validateAndTransformParameter($param, $value);
		}
	}

	/**
	 * @param offset
	 */
	public function offsetExists($offset) {
		$offset = strtolower($offset);
		return in_array($offset, $this->parameterNames);
	}

	/**
	 * @param offset
	 */
	public function offsetGet($offset) {
		$offset = strtolower($offset);
		if (!in_array($offset, $this->parameterNames)) {
			$refObject = new ReflectionObject($this);
			throw new Exception("Invalid parameter: ".$offset." for ".$refObject->getName());
		}
		return isset($this->params[$offset])
			? $this->params[$offset] : null;
	}

	/**
	 * @param offset
	 * @param value
	 */
	public function offsetSet($offset, $value) {
		throw new Exception("Annotations are immutable");
	}

	/**
	 * @param offset
	 */
	public function offsetUnset($offset) {
		throw new Exception("Annotations are immutable");
	}

	/**
	 * {@inheritDoc}
	 */
    public function __set($name, $value) {
		return $this->offsetSet($name, $value);
    }

	/**
	 * {@inheritDoc}
	 */
    public function __get($name) {
		return $this->offsetGet($name);
    }

	/**
	 * {@inheritDoc}
	 */
    public function __isset($name) {
    	$val = $this->offsetGet($name);
		return isset($val);
    }

	/**
	 * {@inheritDoc}
	 */
    public function __unset($name) {
		return $this->offsetUnset($name);
    }
}

?>